package com.example.demo.unique.aop;

import com.baomidou.mybatisplus.annotation.TableName;
import com.example.demo.entity.assist.Unique;
import com.example.demo.unique.annotation.UniqueFiled;
import com.example.demo.unique.annotation.UniqueFlag;
import com.example.demo.unique.dto.UniqueYmlModel;
import com.example.demo.unique.exception.UniqueFileException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @version 1.0
 * @Author: LJP
 * @Description: 字段唯一校验aop处理
 * @Date: Created in 11:59 2022/3/9
 */
@Slf4j
@Aspect
@Component
@EnableConfigurationProperties(UniqueYmlModel.class)
public class UniqueFileAspect {

    private final Connection connection;
    private final UniqueYmlModel uniqueYmlModel;

    private final String MYSQL = "mysql";
    private final String PGSQL = "pgsql";

    public UniqueFileAspect(DataSource dataSource, UniqueYmlModel uniqueYmlModel) throws SQLException {
        connection = dataSource.getConnection();
        this.uniqueYmlModel = uniqueYmlModel;
    }

    @Pointcut("@annotation(com.example.demo.unique.annotation.UniqueFlag)")
    public void pointCut() {
    }

    /**
     * Around advice object.
     *
     * @param proceedingJoinPoint the proceeding join point
     * @return the object
     * @throws Throwable the throwable
     */
    @Around("pointCut()")
    public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
        Method method = signature.getMethod();

        if (Boolean.parseBoolean(uniqueYmlModel.getLogicDelete()) && StringUtils.isBlank(uniqueYmlModel.getDeleteField())) {
            throw new Exception("yml文件中开启了逻辑删除但是字段名未配置");
        }

        if (null != method) {
            //获得请求方式
            UniqueFlag uniqueFlag = method.getAnnotation(UniqueFlag.class);

            //获得操作 （新增/删除）
            String operate = uniqueFlag.value().getValue();

            //判断该方法是否有RequestBody注解
            if (null != operate) {
                //校验逻辑处理部分
                checkFieldIsUnique(proceedingJoinPoint, operate);
            } else {
                return proceedingJoinPoint.proceed();
            }
        }

        return proceedingJoinPoint.proceed();
    }


    /**
     * Check field is unique.
     *
     * @param proceedingJoinPoint the proceeding join point
     * @param operate             operate
     * @throws Exception the exception
     */
    public void checkFieldIsUnique(ProceedingJoinPoint proceedingJoinPoint, String operate) throws Exception {
        //数据Id
        Object id = null;
        boolean flag = true;

        //定义与处理对象和结果集
        PreparedStatement ps = null;
        ResultSet resultSet = null;

        //遍历实体类参数
        for (Object arg : proceedingJoinPoint.getArgs()) {
            if (null != arg) {
                //获得类对象
                Class<?> argClass = arg.getClass();

                //获得表信息
                TableName annotation = argClass.getDeclaredAnnotation(TableName.class);

                //判断是否存在@TableName注解
                if (null != annotation) {
                    //模式名
                    String schema = annotation.schema();
                    //表名
                    String tableName = annotation.value();

                    //获得所有字段
                    List<Field> listFiled = getAllFiled(argClass);

                    //判断数据库类型是mysql还是pgsql  (1、mysql时判断表名不为空即可;2、pgsql时还要判断模式名不为空)
                    boolean sqlFlag = false;

                    if (MYSQL.equals(uniqueYmlModel.getDatabase())) {
                        sqlFlag = StringUtils.isNotBlank(tableName);
                    } else if (PGSQL.equals(uniqueYmlModel.getDatabase())) {
                        sqlFlag = StringUtils.isNotBlank(tableName) && StringUtils.isNotBlank(schema);
                    }

                    if (sqlFlag) {
                        //组合检验字段集合
                        List<Unique> uniques = new ArrayList<>();

                        //循环字段判断是否需要进行唯一校验
                        for (Field field : listFiled) {
                            //打开字段权限
                            field.setAccessible(true);

                            //如果是修改操作则进行数据Id赋值
                            if (operate.equals("update") && flag && field.getName().equals("id")) {
                                id = field.get(arg);
                                flag = false;
                            }

                            UniqueFiled uniqueFile = field.getAnnotation(UniqueFiled.class);

                            //通过判断字段是否包含 @UniqueFile注解来找到要校验的字段
                            if (null != uniqueFile) {

                                //构建字段信息对象
                                Unique uniqueObj = Unique.builder()
                                        .filed(uniqueFile.filed())
                                        .info(uniqueFile.info())
                                        .group(uniqueFile.group())
                                        .build();

                                //需要进行单一校验的字段
                                if (StringUtils.isNotBlank(uniqueObj.getGroup()) && uniqueObj.getGroup().equals("single")) {
                                    //传入sqlSplice()方法进行sql拼接
                                    String sql = mysqlSplice(uniqueYmlModel, operate, tableName, schema, uniqueObj.getFiled());

                                    //创建预处理对象
                                    ps = connection.prepareStatement(sql);

                                    //创建参数集合
                                    List<Object> parameters = new LinkedList<>();

                                    //添加参数
                                    parameters.add(field.get(arg));
                                    if (!flag) {
                                        parameters.add(id);
                                    }

                                    //参数赋值处理
                                    parameterAssignment(ps, parameters);

                                    resultSet = ps.executeQuery();
                                    while (resultSet.next()) {
                                        int count = resultSet.getInt(1);
                                        if (count > 0)
                                            throw new UniqueFileException(uniqueObj.getInfo() + ":" + field.get(arg).toString() + "已存在！");
                                    }

                                } else if (StringUtils.isNotBlank(uniqueObj.getGroup()) && !uniqueObj.getGroup().equals("single")) {
                                    //需要进行组合检验的字段
                                    Unique unique = Unique.builder().filed(uniqueObj.getFiled()).value(field.get(arg)).info(uniqueObj.getInfo()).group(uniqueObj.getGroup()).build();
                                    uniques.add(unique);
                                }

                            }
                        }

                        //sql拼接
                        Function<List<Unique>, String> sqlSplice = item -> {
                            StringBuilder sql = new StringBuilder();

                            for (int i = 0; i < item.size(); i++) {
                                if (i == 0) {
                                    sql.append(item.get(i).getFiled()).append(" = ?");
                                } else {
                                    sql.append(" and ").append(item.get(i).getFiled()).append(" = ?");
                                }
                            }
                            return sql.toString();
                        };

                        //输出语句拼接
                        Function<List<Unique>, String> outputStatement = item -> {
                            StringBuilder info = new StringBuilder();

                            for (Unique unique : item) {
                                info.append(unique.getInfo());
                                info.append(":");
                                info.append(unique.getValue().toString());
                                info.append(",");
                            }

                            if (info.substring(info.length() - 1).equals(",")) {
                                info.deleteCharAt(info.length() - 1);
                            }

                            return info.toString();
                        };


                        //组合检验字段校验逻辑
                        if (uniques.size() > 0) {
                            //根据Group进行分组校验
                            Map<String, List<Unique>> collect = uniques.stream().collect(Collectors.groupingBy(Unique::getGroup));

                            //对每一组中的字段都进行校验
                            for (Map.Entry<String, List<Unique>> entry : collect.entrySet()) {
                                //获得参数
                                List<Object> objectList = entry.getValue().stream().map(Unique::getValue).collect(Collectors.toList());

                                //sql拼接
                                String sql = pgsqlSplice(uniqueYmlModel, tableName, schema);
                                String sqlTwo = sqlSplice.apply(entry.getValue());
                                sql += sqlTwo;

                                if (operate.equals("insert")) {
                                    sql = sql + " ;";
                                } else if (operate.equals("update")) {
                                    sql = sql + " and id != ?;";
                                    objectList.add(id);
                                }

                                //获得预处理对象
                                ps = connection.prepareStatement(sql);

                                //参数赋值
                                parameterAssignment(ps, objectList);

                                //获得结果集
                                resultSet = ps.executeQuery();

                                while (resultSet.next()) {
                                    int count = resultSet.getInt(1);
                                    if (count > 0)
                                        throw new UniqueFileException(outputStatement.apply(entry.getValue()) + "已存在！");
                                }
                            }
                        }
                    }
                }

            }
        }

        //资源关闭
        if (null != resultSet)
            resultSet.close();

        if (null != ps)
            ps.close();
    }

    /**
     * Get all filed list.
     *
     * @param argClass the arg class
     * @return the list
     */
    private List<Field> getAllFiled(Class<?> argClass) {
        //所有字段的集合
        List<Field> listFiled = new ArrayList<>();

        //获得父类
        Class<?> superclass = argClass.getSuperclass();

        //如果存在父类将父类中的字段也放入集合中
        if (null != superclass) {
            //获得父类中的字段
            Field[] declaredFields = superclass.getDeclaredFields();
            listFiled.addAll(Arrays.asList(declaredFields));
        }

        //将所有的字段添加到字段的集合中
        listFiled.addAll(Arrays.asList(argClass.getDeclaredFields()));

        return listFiled;
    }

    /**
     * Parameter assignment.
     *
     * @param ps         the ps
     * @param parameters the parameters
     */
    private void parameterAssignment(PreparedStatement ps, List<Object> parameters) {
        try {
            for (int i = 1; i <= parameters.size(); i++) {
                ps.setObject(i, parameters.get(i - 1));
            }
        } catch (SQLException e) {
            log.error("字段唯一校验时参数赋值出现错误！");
            e.printStackTrace();
        }
    }

    /**
     * Sql splice string.
     *
     * @param uniqueYmlModel the yml model
     * @return the string
     */
    private String mysqlSplice(UniqueYmlModel uniqueYmlModel, String operate, String tableName, String schema, String filed) {
        String sql = "";

        if (MYSQL.equals(uniqueYmlModel.getDatabase())) {
            //mysql时的sql拼接
            if (Boolean.parseBoolean(uniqueYmlModel.getLogicDelete())) {
                //逻辑删除开启时的sql拼接
                if (operate.equals("insert")) {
                    sql = String.format("select count(1) from %s where %s = ? and %s = false;", tableName, filed, uniqueYmlModel.getDeleteField());
                } else if (operate.equals("update")) {
                    sql = String.format("select count(1) from %s where %s = ? and id != ? and %s = false;", tableName, filed, uniqueYmlModel.getDeleteField());
                }
            } else {
                //逻辑删除没开启时的sql拼接
                if (operate.equals("insert")) {
                    sql = String.format("select count(1) from %s where %s = ?;", tableName, filed);
                } else if (operate.equals("update")) {
                    sql = String.format("select count(1) from %s where %s = ? and id != ?;", tableName, filed);
                }
            }
        } else if (PGSQL.equals(uniqueYmlModel.getDatabase())) {
            //pgsql时的sql拼接
            if (Boolean.parseBoolean(uniqueYmlModel.getLogicDelete())) {
                //逻辑删除开启时的sql拼接
                if (operate.equals("insert")) {
                    sql = String.format("select count(1) from %s.%s where %s = ? and %s = false;", schema, tableName, filed, uniqueYmlModel.getDeleteField());
                } else if (operate.equals("update")) {
                    sql = String.format("select count(1) from %s.%s where %s = ? and id != ? and %s = false;", schema, tableName, filed, uniqueYmlModel.getDeleteField());
                }
            } else {
                //逻辑删除没开启时的sql拼接
                if (operate.equals("insert")) {
                    sql = String.format("select count(1) from %s.%s where %s = ?;", schema, tableName, filed);
                } else if (operate.equals("update")) {
                    sql = String.format("select count(1) from %s.%s where %s = ? and id != ?;", schema, tableName, filed);
                }
            }
        }

        return sql;
    }

    /**
     * Pgsql splice string.
     *
     * @param uniqueYmlModel the yml model
     * @param tableName      the table name
     * @param schema         the schema
     * @return the string
     */
    private String pgsqlSplice(UniqueYmlModel uniqueYmlModel, String tableName, String schema) {
        String sql = "";

        if (MYSQL.equals(uniqueYmlModel.getDatabase())) {
            //mysql时的sql拼接
            if (Boolean.parseBoolean(uniqueYmlModel.getLogicDelete())) {
                //逻辑删除开启时的sql拼接
                sql = String.format("select count(1) from %s where %s = false and ", tableName, uniqueYmlModel.getDeleteField());
            } else {
                //逻辑删除没开启时的sql拼接
                sql = String.format("select count(1) from %s where ", tableName);
            }
        } else if (PGSQL.equals(uniqueYmlModel.getDatabase())) {
            //pgsql时的sql拼接
            if (Boolean.parseBoolean(uniqueYmlModel.getLogicDelete())) {
                //逻辑删除开启时的sql拼接
                sql = String.format("select count(1) from %s.%s where %s = false and ", schema, tableName, uniqueYmlModel.getDeleteField());
            } else {
                //逻辑删除没开启时的sql拼接
                sql = String.format("select count(1) from %s.%s where ", schema, tableName);
            }
        }

        return sql;
    }


}
